1
2
3
4
5
6
7
8
9
10
11
12
13 package org.apache.tapestry5.internal.services;
14
15 import org.apache.tapestry5.Asset;
16 import org.apache.tapestry5.SymbolConstants;
17 import org.apache.tapestry5.internal.InternalConstants;
18 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
19 import org.apache.tapestry5.ioc.IOOperation;
20 import org.apache.tapestry5.ioc.OperationTracker;
21 import org.apache.tapestry5.ioc.Resource;
22 import org.apache.tapestry5.ioc.annotations.InjectService;
23 import org.apache.tapestry5.ioc.annotations.Symbol;
24 import org.apache.tapestry5.services.AssetFactory;
25 import org.apache.tapestry5.services.Request;
26 import org.apache.tapestry5.services.Response;
27 import org.apache.tapestry5.services.assets.*;
28
29 import javax.servlet.http.HttpServletResponse;
30 import java.io.IOException;
31 import java.io.OutputStream;
32 import java.util.Set;
33
34 public class ResourceStreamerImpl implements ResourceStreamer
35 {
36 static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since";
37
38 private static final String QUOTE = "\"";
39
40 private final Request request;
41
42 private final Response response;
43
44 private final StreamableResourceSource streamableResourceSource;
45
46 private final boolean productionMode;
47
48 private final OperationTracker tracker;
49
50 private final ResourceChangeTracker resourceChangeTracker;
51
52 private final String omitExpirationCacheControlHeader;
53
54 private final AssetFactory classpathAssetFactory;
55
56 private final AssetFactory contextAssetFactory;
57
58 public ResourceStreamerImpl(Request request,
59
60 Response response,
61
62 StreamableResourceSource streamableResourceSource,
63
64 OperationTracker tracker,
65
66 @Symbol(SymbolConstants.PRODUCTION_MODE)
67 boolean productionMode,
68
69 ResourceChangeTracker resourceChangeTracker,
70
71 @Symbol(SymbolConstants.OMIT_EXPIRATION_CACHE_CONTROL_HEADER)
72 String omitExpirationCacheControlHeader,
73
74 @InjectService("ClasspathAssetFactory")
75 AssetFactory classpathAssetFactory,
76
77 @InjectService("ContextAssetFactory")
78 AssetFactory contextAssetFactory)
79 {
80 this.request = request;
81 this.response = response;
82 this.streamableResourceSource = streamableResourceSource;
83
84 this.tracker = tracker;
85 this.productionMode = productionMode;
86 this.resourceChangeTracker = resourceChangeTracker;
87 this.omitExpirationCacheControlHeader = omitExpirationCacheControlHeader;
88
89 this.classpathAssetFactory = classpathAssetFactory;
90 this.contextAssetFactory = contextAssetFactory;
91 }
92
93 public boolean streamResource(final Resource resource, final String providedChecksum, final Set<Options> options) throws IOException
94 {
95 if (!resource.exists())
96 {
97
98
99 response.sendError(HttpServletResponse.SC_NOT_FOUND, String.format("Unable to locate asset '%s' (the file does not exist).", resource));
100
101 return true;
102 }
103
104 final boolean compress = providedChecksum.startsWith("z");
105
106 return tracker.perform("Streaming " + resource + (compress ? " (compressed)" : ""), new IOOperation<Boolean>()
107 {
108 public Boolean perform() throws IOException
109 {
110 StreamableResourceProcessing processing = compress
111 ? StreamableResourceProcessing.COMPRESSION_ENABLED
112 : StreamableResourceProcessing.COMPRESSION_DISABLED;
113
114 StreamableResource streamable = streamableResourceSource.getStreamableResource(resource, processing, resourceChangeTracker);
115
116 return streamResource(resource, streamable, compress ? providedChecksum.substring(1) : providedChecksum, options);
117 }
118 });
119 }
120
121 public boolean streamResource(StreamableResource streamable, String providedChecksum, Set<Options> options) throws IOException
122 {
123 return streamResource(null, streamable, providedChecksum, options);
124 }
125
126 public boolean streamResource(Resource resource, StreamableResource streamable, String providedChecksum, Set<Options> options) throws IOException
127 {
128 assert streamable != null;
129 assert providedChecksum != null;
130 assert options != null;
131
132 String actualChecksum = streamable.getChecksum();
133
134 if (providedChecksum.length() > 0 && !providedChecksum.equals(actualChecksum))
135 {
136
137
138
139 Asset asset = null;
140 if (resource != null)
141 {
142 asset = findAssetInsideWebapp(resource);
143 }
144 if (asset != null)
145 {
146 response.sendRedirect(asset.toClientURL());
147 return true;
148 }
149 return false;
150 }
151
152
153
154 String token = QUOTE + actualChecksum + QUOTE;
155
156
157
158
159 response.setHeader("ETag", token);
160
161
162
163 String providedToken = request.getHeader("If-None-Match");
164
165 if (token.equals(providedToken))
166 {
167 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
168 return true;
169 }
170
171 long lastModified = streamable.getLastModified();
172
173 long ifModifiedSince;
174
175 try
176 {
177 ifModifiedSince = request.getDateHeader(IF_MODIFIED_SINCE_HEADER);
178 } catch (IllegalArgumentException ex)
179 {
180
181
182 ifModifiedSince = -1;
183 }
184
185 if (ifModifiedSince > 0 && ifModifiedSince >= lastModified)
186 {
187 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
188 return true;
189 }
190
191
192
193 response.disableCompression();
194
195 response.setDateHeader("Last-Modified", lastModified);
196
197
198 if (productionMode && !options.contains(Options.OMIT_EXPIRATION))
199 {
200
201
202 response.setDateHeader("Expires", lastModified + InternalConstants.TEN_YEARS);
203 }
204
205
206
207
208 if (options.contains(Options.OMIT_EXPIRATION))
209 {
210 response.setHeader("Cache-Control", omitExpirationCacheControlHeader);
211 }
212
213 response.setContentLength(streamable.getSize());
214
215 if (streamable.getCompression() == CompressionStatus.COMPRESSED)
216 {
217 response.setHeader(InternalConstants.CONTENT_ENCODING_HEADER, InternalConstants.GZIP_CONTENT_ENCODING);
218 }
219
220 ResponseCustomizer responseCustomizer = streamable.getResponseCustomizer();
221
222 if (responseCustomizer != null)
223 {
224 responseCustomizer.customizeResponse(streamable, response);
225 }
226
227 OutputStream os = response.getOutputStream(streamable.getContentType().toString());
228
229 streamable.streamTo(os);
230
231 os.close();
232
233 return true;
234 }
235
236 private Asset findAssetInsideWebapp(Resource resource)
237 {
238 Asset asset;
239 asset = findAssetFromClasspath(resource);
240 if (asset == null)
241 {
242 asset = findAssetFromContext(resource);
243 }
244 return asset;
245 }
246
247 private Asset findAssetFromContext(Resource resource)
248 {
249 Asset asset = null;
250 try
251 {
252 asset = contextAssetFactory.createAsset(resource);
253 }
254 catch (RuntimeException e)
255 {
256
257 }
258 return asset;
259 }
260
261 private Asset findAssetFromClasspath(Resource resource)
262 {
263 Asset asset = null;
264 try
265 {
266 asset = classpathAssetFactory.createAsset(resource);
267 }
268 catch (RuntimeException e)
269 {
270
271 }
272 return asset;
273 }
274
275 }